<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>第九章：实际应用案例 - 9.3 从文本中提取结构化信息</title>
    <link rel="stylesheet" href="../shared_styles.css">
</head>
<body>
    <div class="container">
        <h1> 第九章：实际应用案例</h1>
        <section id="s9-3">
            <h2>9.3 从文本中提取结构化信息</h2>
            <p>从非结构化文本中提取有用的、结构化的信息是自然语言处理中的一个常见且重要的任务。例如，从产品评论中提取产品名称、优缺点和评分；从新闻文章中提取事件、参与者和时间地点；或者从简历中提取技能、工作经历和教育背景等。</p>
            <p>Langchain结合LLM（如Qwen）的理解能力，可以有效地实现信息提取。核心方法是设计一个提示（Prompt），明确指示LLM需要提取哪些信息以及期望的输出格式（通常是JSON）。</p>
            <p>在本节中，我们将演示如何：</p>
            <ol>
                <li>定义一个Pydantic模型来指定期望的输出结构。</li>
                <li>使用Langchain的<code>PydanticOutputParser</code>来确保LLM的输出符合该结构。</li>
                <li>构建一个链，从给定的文本中提取结构化信息。</li>
            </ol>

            <h3>准备工作：初始化Qwen LLM和必要的导入</h3>
            <pre><code class="language-python">
from langchain_openai import ChatOpenAI # Qwen兼容
from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate
from langchain_core.output_parsers import PydanticOutputParser # 用于解析Pydantic模型
from pydantic import BaseModel, Field # 用于定义数据结构
from typing import List, Optional
from dotenv import load_dotenv
import os

# 加载环境变量
load_dotenv()
api_key = os.environ.get("DASHSCOPE_API_KEY")
if not api_key:
    raise ValueError("DASHSCOPE_API_KEY not found. Please set it in your .env file.")

# 初始化Qwen聊天模型
qwen_llm = ChatOpenAI(
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key=api_key,
    model="qwen-plus", 
    temperature=0.1,   # 对于信息提取，通常需要较低的温度以保证准确性
    streaming=False
)
print("Qwen LLM 初始化成功。")
            </code></pre>

            <h3>案例1：从产品评论中提取信息</h3>
            <p>假设我们有一段产品评论文本，我们想从中提取产品名称、提及的优点、提及的缺点以及一个整体评分（如果评论中提到的话）。</p>
            
            <h4>步骤1：定义Pydantic输出模型</h4>
            <p>我们首先定义一个Pydantic模型，它描述了我们希望LLM输出的数据结构。</p>
            <pre><code class="language-python">
class ProductReviewInfo(BaseModel):
    product_name: Optional[str] = Field(description="评论中提及的产品名称")
    pros: Optional[List[str]] = Field(description="评论中提及的产品优点列表")
    cons: Optional[List[str]] = Field(description="评论中提及的产品缺点列表")
    rating: Optional[int] = Field(description="评论中提及的评分 (1-5星)，如果没有明确提及则不填写")

# 创建Pydantic输出解析器
pydantic_parser = PydanticOutputParser(pydantic_object=ProductReviewInfo)

# 获取格式化指令，这将告诉LLM如何格式化其输出以匹配Pydantic模型
format_instructions = pydantic_parser.get_format_instructions()
# print("Pydantic格式化指令:\n", format_instructions)
            </code></pre>
            <p><code>format_instructions</code>会生成一段文本，指导LLM如何以JSON格式输出，并遵循<code>ProductReviewInfo</code>模型的字段和描述。</p>

            <h4>步骤2：创建提示模板</h4>
            <p>提示模板需要包含原始文本、提取任务的描述以及由<code>PydanticOutputParser</code>生成的格式化指令。</p>
            <pre><code class="language-python">
extraction_prompt_template = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(
        "你是一个专业的信息提取助手。请仔细阅读用户提供的文本，并根据要求提取结构化信息。"
        "请严格按照以下格式指令进行输出。\n{format_instructions}" # 注入格式化指令
    ),
    HumanMessagePromptTemplate.from_template(
        "请从以下产品评论文本中提取产品名称、优点、缺点和评分：\n\n"
        "文本内容：\n"
        "```\n"
        "{text_input}\n"
        "```"
    )
])
            </code></pre>

            <h4>步骤3：构建并运行提取链</h4>
            <pre><code class="language-python">
# 构建LCEL链
# 链的最后一步是pydantic_parser，它会将LLM的JSON字符串输出解析为Pydantic对象实例
extraction_chain = extraction_prompt_template | qwen_llm | pydantic_parser

# 示例文本输入
review_text_1 = """
我最近购买了这款“星云X1”智能手表，体验非常棒！首先，它的电池续航能力超强，
正常使用能坚持5天。其次，屏幕显示清晰细腻，阳光下也能看清。
运动模式也很丰富，记录数据准确。不过，有一点不太满意的是表带材质有点硬，
戴久了不太舒服。另外，APP的同步速度偶尔有点慢。总体来说，我给它打4星。
"""

review_text_2 = """
这款“清风”空气净化器效果一般。优点是噪音确实很小，晚上睡觉几乎听不见。
但是，对于大一点的房间，净化速度感觉跟不上，PM2.5指数下降缓慢。
外观设计还行，比较简约。没有找到评分的地方。
"""

print(f"\n--- 案例1：从产品评论中提取信息 ---")

print(f"\n评论1:\n{review_text_1}")
try:
    extracted_info_1 = extraction_chain.invoke({
        "text_input": review_text_1,
        "format_instructions": format_instructions # 将格式指令传递给模板
    })
    print("\n提取到的信息 (Pydantic对象):")
    print(extracted_info_1)
    if extracted_info_1.product_name:
        print(f"产品名称: {extracted_info_1.product_name}")
    if extracted_info_1.pros:
        print(f"优点: {', '.join(extracted_info_1.pros)}")
    if extracted_info_1.cons:
        print(f"缺点: {', '.join(extracted_info_1.cons)}")
    if extracted_info_1.rating is not None: # rating可以是0，所以检查is not None
        print(f"评分: {extracted_info_1.rating} 星")
except Exception as e:
    print(f"从评论1提取信息时出错: {e}")


print(f"\n评论2:\n{review_text_2}")
try:
    extracted_info_2 = extraction_chain.invoke({
        "text_input": review_text_2,
        "format_instructions": format_instructions
    })
    print("\n提取到的信息 (Pydantic对象):")
    print(extracted_info_2)
    # 同样可以打印具体字段
except Exception as e:
    print(f"从评论2提取信息时出错: {e}")
            </code></pre>
            <p>运行后，<code>extracted_info_1</code>和<code>extracted_info_2</code>将会是<code>ProductReviewInfo</code>的实例，包含了从文本中提取出的结构化数据。如果LLM未能完全遵循格式或某些信息不存在，对应的字段可能是<code>None</code>或空列表。</p>

            <h3>案例2：从新闻摘要中提取关键事件信息</h3>
            <p>我们可以定义另一个Pydantic模型来提取新闻事件的关键要素。</p>
            <pre><code class="language-python">
class NewsEventInfo(BaseModel):
    event_summary: str = Field(description="事件的简要概括")
    participants: Optional[List[str]] = Field(description="事件的主要参与者或组织")
    location: Optional[str] = Field(description="事件发生的地点")
    event_date: Optional[str] = Field(description="事件发生的日期或大致时间")

news_parser = PydanticOutputParser(pydantic_object=NewsEventInfo)
news_format_instructions = news_parser.get_format_instructions()

news_extraction_prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(
        "你是一个高效的新闻分析员。请从提供的新闻摘要中提取关键事件信息。"
        "请严格按照以下格式指令进行输出。\n{format_instructions}"
    ),
    HumanMessagePromptTemplate.from_template(
        "新闻摘要：\n"
        "```\n"
        "{news_text}\n"
        "```"
    )
])

news_extraction_chain = news_extraction_prompt | qwen_llm | news_parser

news_summary_text = """
昨日，科技巨头AlphaCorp在旧金山总部发布了其最新一代AI芯片“NovaCore V3”。
公司CEO张三和首席科学家李四共同出席了发布会。该芯片预计将于今年第四季度上市。
分析人士认为，这将对现有市场格局产生重大影响。
"""

print(f"\n--- 案例2：从新闻摘要中提取关键事件信息 ---")
print(f"\n新闻摘要:\n{news_summary_text}")
try:
    event_data = news_extraction_chain.invoke({
        "news_text": news_summary_text,
        "format_instructions": news_format_instructions
    })
    print("\n提取到的事件信息:")
    print(event_data)
    print(f"事件概括: {event_data.event_summary}")
    if event_data.participants:
        print(f"参与者: {', '.join(event_data.participants)}")
    if event_data.location:
        print(f"地点: {event_data.location}")
    if event_data.event_date:
        print(f"日期: {event_data.event_date}")
except Exception as e:
    print(f"从新闻摘要提取信息时出错: {e}")

            </code></pre>
            
            <h3>总结与提示</h3>
            <ul>
                <li><strong>Pydantic模型是关键：</strong> 清晰地定义Pydantic模型（包括字段描述）对于指导LLM正确输出至关重要。字段描述会包含在给LLM的格式指令中。</li>
                <li><strong>提示工程：</strong> 提示的措辞需要精确。明确告诉LLM它的角色、任务，并强调遵循格式指令。</li>
                <li><strong>处理可选信息：</strong> 使用<code>Optional</code>和默认值（如空列表）来处理文本中可能不存在的信息。</li>
                <li><strong>错误处理和验证：</strong> 尽管<code>PydanticOutputParser</code>会尝试解析LLM的输出，但LLM有时可能不会完美遵循指令。在生产环境中，你可能需要添加额外的错误处理逻辑或重试机制。</li>
                <li><strong>迭代和测试：</strong> 对于复杂的提取任务，可能需要多次迭代提示和Pydantic模型，并用多种示例文本进行测试，以达到最佳效果。</li>
                <li><strong>温度参数：</strong> 对于信息提取这类确定性较高的任务，通常将LLM的<code>temperature</code>参数设置得较低（如0.0-0.2），以减少随机性，提高输出的稳定性和准确性。</li>
            </ul>
            <p>通过这种方法，你可以将Langchain和Qwen模型应用于各种信息提取场景，将大量非结构化文本数据转化为有价值的结构化洞察。</p>
        </section>
        
        <div class="navigation">
            <a href="../langchain_tutorial_outline.html">返回教程目录</a>
            <a href="../chapter9/chapter9_application_text_generation.html">上一节：9.2 实现自动化文本生成</a>
            <!-- <a href="../chapter9/chapter9_application_chatbot.html">下一节：9.4 开发一个具备上下文理解能力的聊天机器人</a> -->
        </div>
    </div>
</body>
</html>
